package com.citycloud.ccuap.tc.oauth2.config;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerSecurityConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import com.citycloud.ccuap.tc.common.result.ResultCodeEnum;
import com.citycloud.ccuap.tc.common.result.ResultObject;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.slf4j.Slf4j;

@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter
		implements ApplicationListener<ContextRefreshedEvent> {

	/** 注入 authenticationManager 来支持 OAuth2 的密码授权模式（password grant type） */
	private final AuthenticationManager authenticationManager;
	private final UserDetailsService userDetailsService;
	private final DataSource dataSource;

	@Autowired
	private AuthorizationCodeServices authorizationCodeServices;


	@Autowired
	private AuthorizationServerSecurityConfiguration authorizationServerSecurityConfiguration;

	private MyClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter;

	private HttpSecurity httpSecurity;

	private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();

	@Autowired
	public AuthorizationServerConfiguration(AuthenticationManager authenticationManager,
			UserDetailsService userDetailsService, DataSource dataSource) {
		this.authenticationManager = authenticationManager;
		this.userDetailsService = userDetailsService;
		this.dataSource = dataSource;
	}

	@Bean
	public TokenStore tokenStore() {
		// 基于 JDBC 实现，令牌保存到数据
		return new JdbcTokenStore(dataSource);
	}

	@Bean
	public ClientDetailsService jdbcClientDetails() {
		// 基于 JDBC 实现，需要事先在数据库配置客户端信息
		return new JdbcClientDetailsService(dataSource);
	}

	@Bean
	public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
		return new JdbcAuthorizationCodeServices(dataSource);
	}

//    @Bean
//    public TokenEnhancer tokenEnhancer() {
//        return new CustomTokenEnhancer();
//    }

	/**
	 * 配置 authenticationManager 来支持 OAuth2 的密码授权模式（password grant type）
	 *
	 */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

		endpoints.tokenStore(tokenStore())
//                .tokenEnhancer(tokenEnhancer())
				.authenticationManager(authenticationManager).userDetailsService(userDetailsService)
				.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).reuseRefreshTokens(true);
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.withClientDetails(jdbcClientDetails());
	}

	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) {
		security
//                .allowFormAuthenticationForClients()
				.addTokenEndpointAuthenticationFilter(myClientCredentialsTokenEndpointFilter());
	}

	/**
	 * 自定义.allowFormAuthenticationForClients实现
	 */
	private MyClientCredentialsTokenEndpointFilter myClientCredentialsTokenEndpointFilter() {
		MyClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new MyClientCredentialsTokenEndpointFilter();
		OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
		authenticationEntryPoint.setTypeName("Form");
		authenticationEntryPoint.setRealmName("oauth2/client");
		clientCredentialsTokenEndpointFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
			@Override
			public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
					AuthenticationException exception) throws IOException, ServletException {
				response.setContentType("application/json; charset=utf-8");
				ObjectMapper mapper = new ObjectMapper();
				String errorMsg = "ClientId/Secret错误，请仔细核对！";
				response.getWriter().print(mapper.writeValueAsString(
						ResultObject.getInstance().error(ResultCodeEnum.SUCCESSFUL_MSG.getCode(), errorMsg)));
			}
		});
		clientCredentialsTokenEndpointFilter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
			@Override
			public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
					Authentication authentication) throws IOException, ServletException {
				// no-op - just allow filter chain to continue to token endpoint
			}
		});
		Field field = null;
		// 通过反射拿到AuthorizationServerSecurityConfiguration中的httpSecurity 先保存起来，因为还没有加载完成，
		try {
			field = WebSecurityConfigurerAdapter.class.getDeclaredField("http");
			field.setAccessible(true);
			this.httpSecurity = (HttpSecurity) field
					.get((WebSecurityConfigurerAdapter) authorizationServerSecurityConfiguration);
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 自动注入的authenticationManager和允许客户端form生成的不一致，故此处不设置，等Spring启动完成，
		// 则通过httpSecurity进行设置
//        clientCredentialsTokenEndpointFilter.setAuthenticationManager(authenticationManager);
		clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
		clientCredentialsTokenEndpointFilter = postProcess(clientCredentialsTokenEndpointFilter);
		// 将自定义的允许客户端通过账号密码form访问类保存
		this.clientCredentialsTokenEndpointFilter = clientCredentialsTokenEndpointFilter;
		return clientCredentialsTokenEndpointFilter;
	}

	protected <T> T postProcess(T object) {
		return (T) this.objectPostProcessor.postProcess(object);
	}

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		log.info("TC_OAUTH2加载完成");
		// 项目加载完成确保httpSecurity中的配置都加载完成，则获取AuthenticationManager，并设置
		this.clientCredentialsTokenEndpointFilter
				.setAuthenticationManager(this.httpSecurity.getSharedObject(AuthenticationManager.class));
	}

	private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> {
		private List<ObjectPostProcessor<?>> postProcessors = new ArrayList<>();

		@Override
		@SuppressWarnings({ "rawtypes", "unchecked" })
		public Object postProcess(Object object) {
			for (ObjectPostProcessor opp : postProcessors) {
				Class<?> oppClass = opp.getClass();
				Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass, ObjectPostProcessor.class);
				if (oppType == null || oppType.isAssignableFrom(object.getClass())) {
					object = opp.postProcess(object);
				}
			}
			return object;
		}

		/**
		 * Adds an {@link ObjectPostProcessor} to use
		 * 
		 * @param objectPostProcessor the {@link ObjectPostProcessor} to add
		 * @return true if the {@link ObjectPostProcessor} was added, else false
		 */
		private boolean addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
			boolean result = this.postProcessors.add(objectPostProcessor);
			postProcessors.sort(AnnotationAwareOrderComparator.INSTANCE);
			return result;
		}
	}
}
